package com.custom.action.joiner;

import com.custom.action.condition.BaseCondition;
import com.custom.action.condition.DefaultColumnParseHandler;
import com.custom.action.condition.support.TableSupport;
import com.custom.action.core.TableInfoCache;
import com.custom.action.core.TableParseModel;
import com.custom.action.core.TableSimpleSupport;
import com.custom.action.interfaces.ColumnParseHandler;
import com.custom.action.joiner.interfaces.DoSomething;
import com.custom.action.util.CustomCharUtil;
import com.custom.action.util.DbUtil;
import com.custom.comm.enums.DbJoinStyle;
import com.custom.comm.enums.SqlAggregate;
import com.custom.comm.enums.SqlExecTemplate;
import com.custom.comm.enums.SqlOrderBy;
import com.custom.comm.exceptions.CustomCheckException;
import com.custom.comm.utils.AssertUtil;
import com.custom.comm.utils.Constants;
import com.custom.comm.utils.CustomUtil;
import com.custom.comm.utils.lambda.LambdaUtil;
import com.custom.comm.utils.lambda.SFunction;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 可实现表连接的条件构造对象
 * @author Xiao-Bai
 * @since 2023/4/30 11:24
 */
public abstract class JoinConditionWrapper<T> extends BaseCondition<T> {


    /**
     * 主表别名
     */
    private String tableAlias = "a";

    /**
     * 查询的selectPrefix(where之前的内容)
     */
    private String selectPrefix;

    /**
     * 表解析信息支持
     */
    private Map<Class<?>, JoinTableCache> joinTableCacheMap;

    /**
     * 字段函数解析缓存
     */
    private Map<Class<?>, ColumnParseHandler> columnParseCacheMap;

    /**
     * join sql
     */
    private List<DoSomething> joinConditionList;

    /**
     * join on condition
     */
    private List<OnCondition> onConditionList;

    /**
     * join Class
     */
    private Class<?> joinClass;

    /**
     * select
     */
    private List<DoSomething> selectColumnList;

    /**
     * select x.*
     */
    private List<Class<?>> selectAllColumnList;

    /**
     * where condition
     */
    private List<DoSomething> conditionList;

    /**
     * groupBy
     */
    private List<DoSomething> groupByList;

    /**
     * orderBy
     */
    private List<DoSomething> orderByList;

    /**
     * having
     */
    private List<DoSomething> havingList;

    /**
     * customize sql
     */
    private List<DoSomething> customizeList;


    protected void wrapperInitialize(Class<T> entityClass, String alias, Map<Class<?>, JoinTableCache> joinTableCacheMap,
                                     Map<Class<?>, ColumnParseHandler> columnParseCacheMap, Class<?> joinClass) {
        this.tableAlias = alias;
        this.joinTableCacheMap = joinTableCacheMap;
        this.columnParseCacheMap = columnParseCacheMap;
        this.wrapperInitialize(entityClass);
        this.joinClass = joinClass;
    }

    protected void wrapperInitialize(Class<T> entityClass, String alias) {
        this.tableAlias = alias;
        this.wrapperInitialize(entityClass);
    }

    protected void wrapperInitialize(Class<T> entityClass) {
        super.wrapperInitialize(entityClass);
        if (this.joinTableCacheMap == null) {
            this.joinTableCacheMap = new HashMap<>();
            // 主表也需要缓存进去，方便查询
            JoinTableCache tableCache = JoinTableCache.of(entityClass, tableAlias);
            this.addJoinTableCacheMap(entityClass, tableCache);
        }
        if (this.columnParseCacheMap == null) {
            this.columnParseCacheMap = new HashMap<>();
            // 当前表的解析也缓存进去
            this.columnParseCacheMap.put(getEntityClass(), getColumnParseHandler());
        }
        this.conditionList = new ArrayList<>();
        this.customizeList = new ArrayList<>();
        this.onConditionList = new ArrayList<>();
        this.selectColumnList = new ArrayList<>();
        this.selectAllColumnList = new ArrayList<>();
        this.groupByList = new ArrayList<>();
        this.orderByList = new ArrayList<>();
        this.havingList = new ArrayList<>();
        this.joinConditionList = new ArrayList<>();
    }

    public String getSelectPrefix() {
        if (CustomUtil.isBlank(this.selectPrefix)) {
            this.selectPrefix = this.getTargetSelectPrefix();
        }
        return this.selectPrefix;
    }

    public String getTableAlias() {
        return tableAlias;
    }

    protected Map<Class<?>, JoinTableCache> getJoinTableCacheMap() {
        return joinTableCacheMap;
    }

    protected List<DoSomething> getSelectColumnList() {
        return selectColumnList;
    }

    protected List<Class<?>> getSelectAllColumnList() {
        return selectAllColumnList;
    }

    public List<DoSomething> getConditionList() {
        return conditionList;
    }

    protected List<DoSomething> getGroupByList() {
        return groupByList;
    }

    protected List<DoSomething> getOrderByList() {
        return orderByList;
    }

    protected List<DoSomething> getHavingList() {
        return havingList;
    }

    protected void collectWhereCondition(DoSomething something) {
        this.conditionList.add(something);
    }

    protected void collectOnCondition(boolean condition, OnCondition onCondition) {
        if (condition) {
            this.onConditionList.add(onCondition);
        }
    }

    @SafeVarargs
    protected final <Any> void collectSelect(SFunction<Any, ?>... columns) {
        collectSelect(Arrays.stream(columns).collect(Collectors.toList()));
    }

    protected <A, B> void collectSelect(SFunction<A, ?> column, SFunction<B, ?> alias) {
        this.selectColumnList.add(() -> {
            String sqlColumn = funcToSqlColumn(column);
            String field = targetColumnParseHandler(alias).parseToField(alias);
            return DbUtil.sqlSelectAsWrapper(sqlColumn, field);
        });
    }

    protected <A, B> void collectSelect(SqlAggregate aggregate, boolean dist, SFunction<A, ?> column, SFunction<B, ?> alias) {
        this.selectColumnList.add(() -> {
            String sqlColumn = funcToSqlColumn(column);
            String field = targetColumnParseHandler(alias).parseToField(alias);
            return aggregate.formatDistinct(dist, sqlColumn, field);
        });
    }

    protected void collectSelectAllColumn(Class<?> anyClass) {
        if (!this.selectAllColumnList.contains(anyClass)) {
            this.selectAllColumnList.add(anyClass);
        }
    }

    protected void collectCustomizeCondition(boolean condition, String customizeSql, Object... params) {
        if (condition) {
            this.customizeList.add(() -> {
                addParams(params);
                return customizeSql;
            });
        }
    }




    @SafeVarargs
    protected final <Any> void collectGroupBy(SFunction<Any, ?>... columns) {
        for (SFunction<Any, ?> column : columns) {
            this.groupByList.add(() -> funcToSqlColumn(column));
        }
    }

    @SafeVarargs
    protected final <Any> void collectOrderBy(boolean condition, SqlOrderBy orderBy, SFunction<Any, ?>... columns) {
        if (condition) {
            for (SFunction<Any, ?> column : columns) {
                String sqlColumn = funcToSqlColumn(column);
                this.orderByList.add(() -> orderBy.format(sqlColumn));
            }
        }
    }

    protected final void collectHaving(boolean condition, DoSomething something) {
        if (condition) {
            this.havingList.add(something);
        }
    }

    /**
     * 查询的列
     */
    protected <Any> void collectSelect(List<SFunction<Any, ?>> columns) {
        if (!columns.isEmpty()) {
            Class<Any> implClass = LambdaUtil.getImplClass(columns.get(0));
            JoinTableCache joinTable = getJoinTable(implClass);
            for (SFunction<Any, ?> column : columns) {
                String colName = targetColumnParseHandler(column).parseToNormalColumn(column);
                this.selectColumnList.add(() -> {
                    String originSqlColumn = DbUtil.fullSqlColumn(joinTable.tableSupport.alias(), colName);
                    String field = joinTable.tableSupport.columnMap().get(originSqlColumn);
                    AssertUtil.checkTrue(field != null,
                            "No field corresponding to SQL column name " + colName + " was found in " + implClass);
                    return DbUtil.sqlSelectAsWrapper(originSqlColumn, field);
                });
            }
        }
    }





    /**
     * 初始化关联条件(暂不支持同一个表的自关联或关联多次)
     */
    protected void initJoinTable(DbJoinStyle joinStyle, JoinConditionWrapper<?> wrapper) {
        String joinAlias;
        // 一样则说明没有给定别名，即可随机生成别名
        if (wrapper.tableAlias.equals(this.tableAlias)) {
            joinAlias = CustomCharUtil.nextStr(6);
        }else {
            joinAlias = wrapper.tableAlias;
        }
        AssertUtil.npe(joinAlias);
        JoinTableCache thisCache = JoinTableCache.of(wrapper.joinClass, joinAlias);
        JoinTableCache existCache = getJoinTableCacheMap(wrapper.joinClass);
        String joinTable = thisCache.tableSupport.table();
        if (existCache != null) {
            // 关联表已存在
            AssertUtil.checkTrue(existCache.tableSupport.table().equals(joinTable),
                    "Table A already has an association, " +
                            "multiple associations with the same table are not supported in queries : " + joinAlias);
            // 别名已存在
            AssertUtil.notEmpty(existCache.joinAlias.equals(joinAlias),
                    "Table alias " + joinAlias + " already exists");
        }else {
            this.addJoinTableCacheMap(wrapper.joinClass, thisCache);
        }
        // on 后面的条件
        AssertUtil.checkTrue(!wrapper.onConditionList.isEmpty(), "关联条件不能为空");
        String onConditionSuffix = this.actionOnConditionSuffix(wrapper);

        this.joinConditionList.add(() -> {
            String finalOnCondition = SqlExecTemplate.format(SqlExecTemplate.JOIN_TABLE, joinStyle.getStyle(),
                    joinTable, joinAlias, DbUtil.trimSqlCondition(onConditionSuffix));
            addParams(wrapper.getParamValues());
            return finalOnCondition;
        });
    }


    protected <R> JoinTableCache getJoinTable(Class<R> joinClass) {
        return getJoinTable(this.joinTableCacheMap, joinClass);
    }

    /**
     * 添加关联表的缓存映射
     * 同时要把实体类的父类也加入进来，否则调用父类的属性时，表别名可能会失踪
     */
    private void addJoinTableCacheMap(Class<?> joinClass, JoinTableCache cache) {
        if (joinClass.equals(Object.class)) {
            return;
        }
        this.joinTableCacheMap.put(joinClass, cache);
        addJoinTableCacheMap(joinClass.getSuperclass(), cache);
    }

    private JoinTableCache getJoinTableCacheMap(Class<?> joinClass) {
       return joinTableCacheMap.get(joinClass);
    }

    /**
     * 获取关联表的信息
     */
    protected <R> JoinTableCache getJoinTable(Map<Class<?>, JoinTableCache> joinCacheMap, Class<R> joinClass) {
        JoinTableCache joinTableCache = joinCacheMap.get(joinClass);
        if (joinTableCache == null) {
            throw new CustomCheckException("关联对象 [%s] 未在本次关联中定义或初始化");
        }
        return joinTableCache;
    }


    private <Any> ColumnParseHandler targetColumnParseHandler(SFunction<Any, ?> func) {
        Class<Any> implClass = LambdaUtil.getImplClass(func);
        if (!columnParseCacheMap.containsKey(implClass)) {
            //todo 可以做一些优化，将这些缓存起来予以共享
            TableSupport tableSupport = new TableSimpleSupport<>(implClass);
            ColumnParseHandler columnParseHandler = new DefaultColumnParseHandler(implClass, tableSupport);
            columnParseCacheMap.put(implClass, columnParseHandler);
        }
        return this.columnParseCacheMap.get(implClass);
    }

    protected <Any> String funcToSqlColumn(SFunction<Any, ?> func) {
        return funcToSqlColumn(this.joinTableCacheMap, func);
    }


    /**
     * 解析函数，返回对应SQL字段
     */
    protected <Any> String funcToSqlColumn(Map<Class<?>, JoinTableCache> cacheMap, SFunction<Any, ?> func) {
        Class<Any> implClass = LambdaUtil.getImplClass(func);
        JoinTableCache joinTable = getJoinTable(cacheMap, implClass);
        String column = targetColumnParseHandler(func).parseToNormalColumn(func);
        if (Constants.EMPTY.equals(joinTable.joinAlias)) {
            return column;
        }
        return DbUtil.fullSqlColumn(joinTable.joinAlias, column);
    }


    /**
     * 后置条件
     * 作用于大括号中 on a.id = b.id {and b.type = ?}
     */
    private String actionOnConditionSuffix(JoinConditionWrapper<?> wrapper) {
        OnCondition condition = wrapper.onConditionList.get(0);
        List<DoSomething> somethings = wrapper.conditionList;
        String doing = somethings.stream().map(DoSomething::getSomething).collect(Collectors.joining(Constants.WHITESPACE));
        if (CustomUtil.isBlank(doing)) {
            return condition.action(this.joinTableCacheMap);
        }
        return condition.action(this.joinTableCacheMap) + doing;
    }


    /**
     * 查询sql拼接
     */
    private String getTargetSelectPrefix() {

        // 1. select
        // 若查询的
        List<String> selectSqlColumnList = new ArrayList<>();
        for (Class<?> selectClass : selectAllColumnList) {
            TableParseModel<?> tableModel = TableInfoCache.getTableModel(selectClass);
            JoinTableCache joinTable = getJoinTable(selectClass);

            // select all column
            tableModel.getDbFieldParseModels()
                    .forEach(e -> {
                        String column = DbUtil.fullSqlColumn(joinTable.joinAlias, e.getColumn());
                        String selectColumn = DbUtil.sqlSelectAsWrapper(column, e.getFieldName());
                        selectSqlColumnList.add(selectColumn);
                    });
        }

        selectColumnList.stream().map(DoSomething::getSomething).forEach(selectSqlColumnList::add);
        String targetSelectSql = String.join(Constants.SEPARATOR_COMMA_2, selectSqlColumnList);

        // 若未指定查询的字段，则给定默认的查询字段(主表的所有字段)
        if (CustomUtil.isBlank(targetSelectSql)) {
            return Constants.EMPTY;
        }

        // 2. from
        String selectSql = SqlExecTemplate.format(SqlExecTemplate.SELECT_LIST,
                targetSelectSql, getTableSupport().table(), tableAlias);

        // 3.join
        selectSql += joinConditionList.stream()
                .map(DoSomething::getSomething).collect(Collectors.joining());

        return selectSql;
    }


    /**
     * 获取group by以及后面的条件
     */
    public void appendConditionSuffix(StringBuilder selectSql) {
        // group by
        if (!groupByList.isEmpty()) {
            selectSql.append(Constants.GROUP_BY);
            String groupBy = groupByList.stream()
                    .map(DoSomething::getSomething)
                    .collect(Collectors.joining(Constants.SEPARATOR_COMMA_2));
            selectSql.append(groupBy);
        }
        // having
        if (!havingList.isEmpty()) {
            selectSql.append(Constants.HAVING);
            String having = havingList.stream()
                    .map(DoSomething::getSomething)
                    .collect(Collectors.joining(Constants.WHITESPACE));
            selectSql.append(having);
        }
        // order by
        if (!orderByList.isEmpty()) {
            selectSql.append(Constants.ORDER_BY);
            String orderBy = orderByList.stream()
                    .map(DoSomething::getSomething)
                    .collect(Collectors.joining(Constants.SEPARATOR_COMMA_2));
            selectSql.append(orderBy);
        }
    }

    /**
     * 条件拼接
     */
    public String getFinalConditional() {
        // where之后 group by之前的条件
        if (CustomUtil.isBlank(super.getFinalConditional())) {
            for (DoSomething something : conditionList) {
                addCondition(something.getSomething());
            }
        }
        String customSql = this.customizeList.stream().map(DoSomething::getSomething)
                .collect(Collectors.joining(Constants.SEPARATOR_COMMA_2));
        addCustomizeSql(customSql);
        return super.getFinalConditional();
    }

    public Map<Class<?>, ColumnParseHandler> getColumnParseCacheMap() {
        return columnParseCacheMap;
    }


    /**
     * 关联表存储信息
     */
    protected static class JoinTableCache {

        /**
         * 关联表别名
         */
        private final String joinAlias;

        /**
         * 表信息支持
         */
        private final TableSupport tableSupport;

        private JoinTableCache(Class<?> joinClass, String joinAlias) {
            this.joinAlias = joinAlias;
            this.tableSupport = new TableSimpleSupport<>(joinClass);
        }

        public static JoinTableCache of(Class<?> joinClass, String alias) {
            return new JoinTableCache(joinClass, alias);
        }

    }

    @FunctionalInterface
    protected interface OnCondition {
        /**
         * 关联条件函数接口
         */
        String action(Map<Class<?>, JoinTableCache> cache);

    }

}
